/*
 * Copyright (c) 2010, Nikolaus Moll
 * All rights reserved.
 */
package net.miblounge.gradecalculator.core.descriptors;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import net.miblounge.gradecalculator.core.descriptors.courses.Course;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.IndividualModule;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.Module;
import net.miblounge.gradecalculator.core.descriptors.modulelistitems.SelectableModuleLists;
import net.miblounge.gradecalculator.core.descriptors.modulelists.ModuleList;
import net.miblounge.gradecalculator.core.enums.CourseTypeDescriptor;
import net.miblounge.gradecalculator.core.enums.ExaminationTypeDescriptor;
import net.miblounge.gradecalculator.core.interfaces.IModuleListItem;
import net.miblounge.gradecalculator.exceptions.XMLException;

@XmlRootElement(namespace = "http://gradecalculator.net/")
@XmlSeeAlso({Module.class, IndividualModule.class, SelectableModuleLists.class})
@XmlType( 
 propOrder = { "university", "degree", "semester", "courseTypes", "examTypes", "moduleList" } 
) 
public class StudyRegulationsDescription  {
	private String university;
	private String degree;
	private String semester;
	private ModuleList moduleList;

	private List<CourseTypeDescriptor> courseTypes;
	private List<ExaminationTypeDescriptor> examTypes;

	
	public StudyRegulationsDescription()
	{
		university = "";
		degree = "";
		semester = "";
		moduleList = new ModuleList();
	}
	
	public String getUniversity() {
		return university;
	}
	public void setUniversity(final String university) {
		this.university = university;
	}
	
	public ModuleList getModuleList() {
		return moduleList;
	}
	public void setModuleList(final ModuleList moduleList) {
		this.moduleList = moduleList;
	}

	@XmlTransient
	public float getECTS() {
		return moduleList.getECTS();
	}

	@XmlTransient
	public int getHoursPerWeek() {
		return moduleList.getHoursPerWeek();
	}

	public String getDegree() {
		return degree;
	}
	public void setDegree(final String degree) {
		if (degree == null) {
			this.degree = "";
		} else {
			this.degree = degree;
		}
	}
	
	public String getSemester() {
		return semester;
	}
	public void setSemester(final String semester) {
		if (semester == null) {
			this.semester = "";
		} else {
			this.semester = semester;
		}
	}
	
	public List<Course> getCourseByName(final String courseName)
	{
		return getCourseByName(moduleList, courseName);
	}

	public static List<Course> getCourseByName(final ModuleList moduleList, final String courseName)
	{
		final List<Course> result = new ArrayList<Course>();
		
		for (final IModuleListItem module : moduleList.getModules()) {
			if (module instanceof IndividualModule) {
				continue;
			}
			
			if (module instanceof Module) {
				result.addAll(getCourseByName((Module)module, courseName));
			}

			if (module instanceof SelectableModuleLists) {
				result.addAll(getCourseByName((SelectableModuleLists)module, courseName));
			}
		}
		
		return result;
	}

	private static List<Course> getCourseByName(final Module module, final String courseName) {
		final List<Course> result = new ArrayList<Course>();
		for (final Course course : module.getCourses()) {
			if (course.getName().equals(courseName)) {
				result.add(course);
			}
		}
		
		return result;
	}

	private static List<Course> getCourseByName(final SelectableModuleLists moduleLists, final String courseName) {
		final List<Course> result = new ArrayList<Course>();
		for (final ModuleList list : moduleLists.getModuleLists()) {
			result.addAll(getCourseByName(list, courseName));
		}
		
		return result;
	}
	
	public Course getCourseByExamNumber(final int examNumber)
	{
		return getCourseByExamNumber(moduleList, examNumber);
	}

	private static Course getCourseByExamNumber(final ModuleList moduleList, final int examNumber)
	{
		for (final IModuleListItem module : moduleList.getModules()) {
			if (module instanceof IndividualModule) {
				continue;
			}
			
			if (module instanceof Module) {
				final Course result = getCourseByExamNumber((Module)module, examNumber);
				if (result != null) {
					return result;
				}
			}

			if (module instanceof SelectableModuleLists) {
				final Course result = getCourseByExamNumber((SelectableModuleLists)module, examNumber);
				if (result != null) {
					return result;
				}
			}
		}
		
		return null;
	}

	private static Course getCourseByExamNumber(final Module module, final int examNumber) {
		for (final Course course : module.getCourses()) {
			if (course.getExamNumber() == examNumber) {
				return course;
			}
		}
		
		return null;
	}

	private static Course getCourseByExamNumber(final SelectableModuleLists moduleLists, final int examNumber) {
		for (final ModuleList list : moduleLists.getModuleLists()) {
			final Course result = getCourseByExamNumber(list, examNumber);
			if (result != null) {
				return result;
			}
		}
		
		return null;
	}
	
	@XmlElementWrapper(name="coursetypes")
	@XmlElement(name = "coursetype")
	public List<CourseTypeDescriptor> getCourseTypes() {
		return courseTypes;
	}
	public void setCourseTypes(final List<CourseTypeDescriptor> courseTypes) {
		this.courseTypes = courseTypes;
	}
	
	@XmlElementWrapper(name="examtypes")
	@XmlElement(name = "examtype")
	public List<ExaminationTypeDescriptor> getExamTypes() {
		return examTypes;
	}
	public void setExamTypes(final List<ExaminationTypeDescriptor> examTypes) {
		
		this.examTypes = examTypes;
	}

	public static StudyRegulationsDescription createFromXML(final String filename) {
		StudyRegulationsDescription result = null;
		Reader xmlinput = null;
		try {
			final JAXBContext context = JAXBContext.newInstance(StudyRegulationsDescription.class);
			final Unmarshaller unmarshaller = context.createUnmarshaller();
			xmlinput = new FileReader(filename);
			
			result = (StudyRegulationsDescription) unmarshaller.unmarshal(xmlinput);

			XmlTreeHelper.updateParent(result.moduleList);
		} catch (final JAXBException jaxbException) {
			throw new XMLException("Error reading regulations xml", jaxbException);
		} catch (final Exception e) {
			throw new XMLException("Error reading regulations xml", e);
		} finally {
			try {
				if (xmlinput != null) {
					xmlinput.close();
				}
			} catch (final IOException e) {
				throw new XMLException("Error closing regulations xml", e);
			}
		}
		
		return result;
	}
	
	public void saveXML(final String filename) {
		Writer xmloutput = null;
		try {
			final JAXBContext context = JAXBContext.newInstance(StudyRegulationsDescription.class);
			final Marshaller marshaller = context.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
			xmloutput = new FileWriter(filename);
			marshaller.marshal(this, xmloutput);
			xmloutput.flush();
		} catch (final JAXBException jaxbException) {
			throw new XMLException("Error writing regulations xml", jaxbException);
		} catch (final Exception e) {
			throw new XMLException("Error saving regulations xml", e);
		} finally {
			try {
				if (xmloutput != null) {
					xmloutput.close();
				}
			} catch (final Exception e) {
				throw new XMLException("Error closing regulations xml", e);
			}
		}
	}
	
	@Override
	public String toString()
	{
		return getModuleList().getName() + " [" + degree + "]";
	}
	
	@Override
	public int hashCode()
	{
		int result = 17;
		result = 31 * result + university.hashCode();
		result = 31 * result + degree.hashCode();
		result = 31 * result + semester.hashCode();
		result = 31 * result + moduleList.hashCode();
		
		return result;
	}
}

final class XmlTreeHelper {
	private XmlTreeHelper()
	{
	}

	private static void updateModule(final Module m) {
		for (Course c : m.getCourses()) {
			c.setParent(m);
		}
	}
	
	private static void updateSelectableModuleLists(final SelectableModuleLists sml)
	{
		for (ModuleList l : sml.getModuleLists()) {
			l.setParent(sml);
			updateParent(l);
		}
	}

	public static void updateParent(final ModuleList ml) {
		for (IModuleListItem imodule : ml.getModules()) {
			imodule.setParent(ml);
			switch (imodule.getModuleType()) {
			case Module:
				final Module m = (Module) imodule;
				updateModule(m);
				break;

			case IndividualModule:
				final IndividualModule im = (IndividualModule) imodule;
				updateModule(im);
				break;

			case SelectableModuleLists:
				final SelectableModuleLists sml = (SelectableModuleLists) imodule;
				updateSelectableModuleLists(sml);
				break;

			default:
			}
		}
	}
}

